using Engine.AddinManager;
using Engine.Graphics;
using street.AddinManager;
using street.GameDllInterfaces;
using System;
using System.Collections.Generic;
using System.IO;

namespace street
{
    public struct Properties
    {
        #region Private Fields

        private const byte Version = 0x04;
        private Dictionary<IStreetAddin, DataForGamevertexCalclation> gameVertexCreationData;
        private int inclinationEnd;
        private int inclinationMiddle;
        private int inclinationStart;
        private IMaterial materialForRail;
        private IMaterial materialForRoad;
        private int railLeftEnd;
        private int railLeftMiddle;
        private int railLeftStart;
        private int raiRightEnd;
        private int raiRightMiddle;
        private int raiRightStart;
        private float streetwidthEnd;
        private float streetwidthStart;

        #endregion Private Fields

        #region Public Constructors

        public Properties(Properties properties)
        {
            this.gameVertexCreationData = new Dictionary<IStreetAddin, DataForGamevertexCalclation>();
            this.streetwidthStart = properties.streetwidthStart;
            this.streetwidthEnd = properties.streetwidthEnd;
            this.inclinationStart = properties.inclinationStart;
            this.inclinationEnd = properties.inclinationEnd;
            this.inclinationMiddle = properties.inclinationMiddle;
            this.railLeftMiddle = properties.railLeftMiddle;
            this.railLeftStart = properties.railLeftStart;
            this.railLeftEnd = properties.railLeftEnd;
            this.raiRightStart = properties.raiRightStart;
            this.raiRightMiddle = properties.raiRightMiddle;
            this.raiRightEnd = properties.raiRightEnd;

            this.materialForRoad = properties.materialForRoad;
            this.materialForRail = properties.materialForRail;

            // Copy game vertex data
            foreach (var item in properties.gameVertexCreationData.Keys)
            {
                DataForGamevertexCalclation data;
                if (properties.gameVertexCreationData.TryGetValue(item, out data))
                    this.gameVertexCreationData.Add(item, data);
            }
        }

        public Properties(float streetwidth)
        {
            throw new NotImplementedException("dfsd");
            //this.streetwidthStart = streetwidth;
            //this.streetwidthEnd = streetwidth;
            //this.inclinationStart = 0;
            //this.inclinationMiddle = 0;
            //this.inclinationEnd = 0;
            //this.railLeftStart = 0;
            //this.railLeftMiddle = 0;
            //this.railLeftEnd = 0;
            //this.raiRightStart = 0;
            //this.raiRightMiddle = 0;
            //this.raiRightEnd = 0;
            //this.colorStart = Color.Black;
            //this.colorEnd = Color.Black;
            //this.colorLeft = Color.Black;
            //this.colorRight = Color.Black;
            //this.materialForRail = new Material();//"empty", "empty", "empty", EnumMappingMode.material144);
            //this.materialForRoad = new Material();//"empty", "empty", "empty", EnumMappingMode.material144);
        }

        #endregion Public Constructors

        #region Public Properties

        public int InclinationEnd
        {
            get { return this.inclinationEnd; }
            set { street.streetTool.hasPropertiesChanged = true; this.inclinationEnd = value; }
        }

        public int InclinationMiddle
        {
            get { return this.inclinationMiddle; }
            set { this.inclinationMiddle = value; }
        }

        public int InclinationStart
        {
            get { return this.inclinationStart; }
            set { street.streetTool.hasPropertiesChanged = true; this.inclinationStart = value; }
        }

        public IMaterial MaterialForRail
        {
            get { return this.materialForRail; }
            set { this.materialForRail = value; }
        }

        public IMaterial MaterialForRoad
        {
            get { return this.materialForRoad; }
            set { this.materialForRoad = value; }
        }

        public int RailLeftEnd
        {
            get { return this.railLeftEnd; }
            set { street.streetTool.hasPropertiesChanged = true; this.railLeftEnd = value; }
        }

        public int RailLeftMiddle
        {
            get { return this.railLeftMiddle; }
            set { street.streetTool.hasPropertiesChanged = true; this.railLeftMiddle = value; }
        }

        public int RailLeftStart
        {
            get { return this.railLeftStart; }
            set { street.streetTool.hasPropertiesChanged = true; this.railLeftStart = value; }
        }

        public int RaiRightEnd
        {
            get { return this.raiRightEnd; }
            set { street.streetTool.hasPropertiesChanged = true; this.raiRightEnd = value; }
        }

        public int RaiRightMiddle
        {
            get { return this.raiRightMiddle; }
            set { street.streetTool.hasPropertiesChanged = true; this.raiRightMiddle = value; }
        }

        public int RaiRightStart
        {
            get { return this.raiRightStart; }
            set { street.streetTool.hasPropertiesChanged = true; this.raiRightStart = value; }
        }

        public float StreetwidthEnd
        {
            get { return this.streetwidthEnd; }
            set { street.streetTool.hasPropertiesChanged = true; this.streetwidthEnd = value; }
        }

        public float StreetwidthStart
        {
            get { return this.streetwidthStart; }
            set { street.streetTool.hasPropertiesChanged = true; this.streetwidthStart = value; }
        }

        #endregion Public Properties

        #region Internal Methods

        internal static Properties FromStream(BinaryReader br, Engine.AddinManager.IAddin addin,
            AddinManager.AddinManagerStreetAddin addinmanager, MaterialManager materialManager)
        {
            Properties p = new Properties();
            var worldRacing2AddinForVertexCreation = addinmanager.GetAddin("World Racing 2");

            switch (br.ReadByte())
            {
                case 0x01:
                    p = LoadV1Properties(br, addin, worldRacing2AddinForVertexCreation, materialManager);
                    break;

                case 0x02:
                    p = LoadV2Properties(br, addin, worldRacing2AddinForVertexCreation, materialManager);
                    break;

                case 0x03:
                    p = LoadV3Properties(br, addin, worldRacing2AddinForVertexCreation, materialManager);
                    break;

                case 0x04:
                    p = LoadV4Properties(br, addin, addinmanager, materialManager);
                    break;

                default:
                    br.BaseStream.Position -= 1;
                    p = LoadV0Properties(br, addin, worldRacing2AddinForVertexCreation, materialManager);
                    break;
            }
            return p;
        }

        internal DataForGamevertexCalclation GetGameProperties(IStreetAddin addin)
        {
            DataForGamevertexCalclation data;

            if (this.gameVertexCreationData.TryGetValue(addin, out data))
                return data;

            throw new Exception("No Data for addin: " + addin.GetGameName());
        }

        /// <summary>
        /// Adds or change data for the given addin.
        /// </summary>
        /// <param name="addinScenarioEditor"></param>
        /// <param name="dataForGamevertexCalclation"></param>
        internal void SetGameVertexData(IStreetAddin addinScenarioEditor, DataForGamevertexCalclation dataForGamevertexCalclation)
        {
            if (this.gameVertexCreationData == null)
                this.gameVertexCreationData = new Dictionary<IStreetAddin, DataForGamevertexCalclation>();

            if (this.gameVertexCreationData.ContainsKey(addinScenarioEditor))
                this.gameVertexCreationData[addinScenarioEditor] = dataForGamevertexCalclation;
            else
                this.gameVertexCreationData.Add(addinScenarioEditor, dataForGamevertexCalclation);
        }

        internal void ToStream(BinaryWriter writer)
        {
            // Road properties
            writer.Write(Version);
            writer.Write(this.streetwidthStart);
            writer.Write(this.streetwidthEnd);
            writer.Write(this.inclinationStart);
            writer.Write(this.inclinationMiddle);
            writer.Write(this.inclinationEnd);

            // Rail left
            writer.Write(this.railLeftStart);
            writer.Write(this.railLeftMiddle);
            writer.Write(this.railLeftEnd);

            // Rail right
            writer.Write(this.raiRightStart);
            writer.Write(this.raiRightMiddle);
            writer.Write(this.raiRightEnd);

            // Materials
            this.materialForRoad.SaveToStream(writer);
            this.materialForRail.SaveToStream(writer);

            // Write number of gameaddins.
            writer.Write((int)this.gameVertexCreationData.Keys.Count);

            foreach (IStreetAddin addin in this.gameVertexCreationData.Keys)
            {
                // Crate a temporary binarywriter to get the data for road created by addin.
                using (MemoryStream ms = new MemoryStream())
                using (BinaryWriter bwData = new BinaryWriter(ms))
                {
                    DataForGamevertexCalclation data;

                    if (this.gameVertexCreationData.TryGetValue(addin, out data))
                    {
                        // Save data to stream
                        addin.SaveStreetData(data, bwData);
                        // Set the datasize.
                        data.sizeInBytes = (int)bwData.BaseStream.Length;
                    }
                    else
                    {
                        // Save empty Properties
                        data = new DataForGamevertexCalclation()
                        {
                            sizeInBytes = 0,
                            version = 0
                        };
                    }

                    // Write data to file.
                    writer.Write(addin.GetGameName());
                    writer.Write((int)data.sizeInBytes);
                    writer.Write((int)data.version);
                    // Write gamedll data which was stored in memory stream.
                    using (BinaryReader br = new BinaryReader(ms))
                    {
                        ms.Position = 0;
                        writer.Write(br.ReadBytes((int)br.BaseStream.Length));
                    }
                }
            }
        }

        #endregion Internal Methods

        #region Private Methods

        private static Properties LoadV0Properties(BinaryReader br, Engine.AddinManager.IAddin addin,
             IStreetAddin streetAddin, MaterialManager materialManager)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();

            // Read old Wr2 Vertex colors
            ReadOldWr2VertexColors(br, streetAddin, ref p, 0);

            //p.ColorStart = Color.Black;
            //p.ColorEnd = Color.Black;
            //p.ColorLeft = Color.Black;
            //p.ColorRight = Color.Black;

            p.materialForRoad = addin.LoadMaterial(br, materialManager);
            p.materialForRail = addin.LoadMaterial(br, materialManager);
            return p;
        }

        private static Properties LoadV1Properties(BinaryReader br, Engine.AddinManager.IAddin addin,
            IStreetAddin streetAddin, MaterialManager materialManager)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            // Read old Wr2 Vertex colors
            ReadOldWr2VertexColors(br, streetAddin, ref p, 0);

            //p.ColorStart = Color.Black;
            //p.ColorEnd = Color.Black;
            //p.ColorLeft = Color.Black;
            //p.ColorRight = Color.Black;

            p.materialForRoad = addin.LoadMaterial(br, materialManager);
            p.materialForRail = addin.LoadMaterial(br, materialManager);
            return p;
        }

        private static Properties LoadV2Properties(BinaryReader br, Engine.AddinManager.IAddin addin,
            IStreetAddin streetAddin, MaterialManager materialManager)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            // Read old Wr2 Vertex colors
            ReadOldWr2VertexColors(br, streetAddin, ref p, 1);

            //p.ColorStart = Color.FromArgb(br.ReadInt32());
            //p.ColorEnd = Color.FromArgb(br.ReadInt32());
            //p.ColorLeft = Color.FromArgb(br.ReadInt32());
            //p.ColorRight = Color.FromArgb(br.ReadInt32());

            p.materialForRoad = addin.LoadMaterial(br, materialManager);
            p.materialForRail = addin.LoadMaterial(br, materialManager);

            // Calculate inclination at the middle of the road.
            p.InclinationMiddle = (p.inclinationStart + p.InclinationEnd) / 2;
            return p;
        }

        private static Properties LoadV3Properties(BinaryReader br, Engine.AddinManager.IAddin addin,
            IStreetAddin streetAddin, MaterialManager materialManager)
        {
            Properties p = new Properties();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationMiddle = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            // Read old Wr2 Vertex colors
            ReadOldWr2VertexColors(br, streetAddin, ref p, 1);

            //p.ColorStart = Color.FromArgb(br.ReadInt32());
            //p.ColorEnd = Color.FromArgb(br.ReadInt32());
            //p.ColorLeft = Color.FromArgb(br.ReadInt32());
            //p.ColorRight = Color.FromArgb(br.ReadInt32());

            p.materialForRoad = addin.LoadMaterial(br, materialManager);
            p.materialForRail = addin.LoadMaterial(br, materialManager);
            return p;
        }

        private static Properties LoadV4Properties(BinaryReader br, IAddin addin, AddinManagerStreetAddin addinmanager, MaterialManager materialManager)
        {
            Properties p = new Properties();
            p.gameVertexCreationData = new Dictionary<IStreetAddin, DataForGamevertexCalclation>();
            p.streetwidthStart = br.ReadSingle();
            p.streetwidthEnd = br.ReadSingle();
            p.inclinationStart = br.ReadInt32();
            p.inclinationMiddle = br.ReadInt32();
            p.inclinationEnd = br.ReadInt32();
            p.railLeftStart = br.ReadInt32();
            p.railLeftMiddle = br.ReadInt32();
            p.railLeftEnd = br.ReadInt32();
            p.raiRightStart = br.ReadInt32();
            p.raiRightMiddle = br.ReadInt32();
            p.raiRightEnd = br.ReadInt32();

            p.materialForRoad = addin.LoadMaterial(br, materialManager);
            p.materialForRail = addin.LoadMaterial(br, materialManager);

            int numberOfVertexDataAddins = br.ReadInt32();

            for (int i = 0; i < numberOfVertexDataAddins; i++)
            {
                string addinName = br.ReadString();
                int sizeInBytes = br.ReadInt32();
                int version = br.ReadInt32();

                long currentPositionInStream = br.BaseStream.Position;

                var streetAddin = addinmanager.GetAddin(addinName);

                p.SetGameVertexData(streetAddin, streetAddin.ReadStreetAddinData(br, version));

                br.BaseStream.Position = currentPositionInStream + sizeInBytes;
            }

            return p;
        }

        private static void ReadOldWr2VertexColors(BinaryReader br,
            IStreetAddin streetAddin, ref Properties properties, int version)
        {
            if (properties.gameVertexCreationData == null)
                properties.gameVertexCreationData = new Dictionary<IStreetAddin, DataForGamevertexCalclation>();

            long currentBaseStreamPosition = br.BaseStream.Position;
            if (streetAddin.GetGameName() == "World Racing 2")
            {
                DataForGamevertexCalclation data = streetAddin.ReadStreetAddinData(br, version);
                properties.gameVertexCreationData.Add(streetAddin, data);
            }
            else
                throw new NotImplementedException("Only old World Racing 2 addin works with old stored data!");
            br.BaseStream.Position = currentBaseStreamPosition + 16;
        }

        #endregion Private Methods
    }
}